package com.coder.books.thinkinjava.concurrency.restaurant2;


import com.coder.books.thinkinjava.enumerated.menu.Course;
import com.coder.books.thinkinjava.enumerated.menu.Food;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

import static com.coder.util.Print.println;

/**
 * 饭店仿真
 *
 * @author yuhushuan
 * @date 2020/9/6 10:40
 */


// 订单，这是给服务器的，服务器给厨师
class Order {
    private static int counter = 0;
    private final int id = counter++;
    private final Customer customer;
    private final WaitPerson waitPerson;
    private final Food food;

    public Order(Customer customer, WaitPerson waitPerson, Food food) {
        this.customer = customer;
        this.waitPerson = waitPerson;
        this.food = food;
    }

    public Food item() {
        return food;
    }

    public Customer getCustomer() {
        return customer;
    }

    public WaitPerson getWaitPerson() {
        return waitPerson;
    }

    @Override
    public String toString() {
        return "Order: " + id + " item: " + food + " for: " + customer + " served by: " + waitPerson;
    }
}

// 盘子，厨师的回馈
class Plate {
    private final Order order;
    private final Food food;

    public Plate(Order order, Food food) {
        this.order = order;
        this.food = food;
    }

    public Order getOrder() {
        return order;
    }

    public Food getFood() {
        return food;
    }

    @Override
    public String toString() {
        return food.toString();
    }
}

// 顾客
class Customer implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final WaitPerson waitPerson;

    // 任何时刻只能上一道菜
    private SynchronousQueue<Plate> placeSetting = new SynchronousQueue<>();

    public Customer(WaitPerson waitPerson) {
        this.waitPerson = waitPerson;
    }

    public void deliver(Plate plate) throws InterruptedException {
        // 仅在客户仍在吃上一道菜的情况下阻止
        placeSetting.put(plate);
    }

    @Override
    public void run() {
        for (Course course : Course.values()) {
            Food food = course.randomSelection();
            try {
                waitPerson.placeOrder(this, food);
                // 在菜品交付之前一直阻塞
                println(this + "eating " + placeSetting.take());
            } catch (InterruptedException e) {
                println(this + "waiting for " + course + " interrupted");
                break;
            }
        }
        println(this + "finished meal, leaving");
    }

    @Override
    public String toString() {
        return "Customer " + id + " ";
    }
}

// 服务员
class WaitPerson implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final Restaurant restaurant;
    BlockingQueue<Plate> filledOrders = new LinkedBlockingDeque<>();

    public WaitPerson(Restaurant restaurant) {
        this.restaurant = restaurant;
    }

    // 下订单
    public void placeOrder(Customer customer, Food food) {
        try {
            // 实际上不应该阻止，因为这是一个没有大小限制的LinkedBlockingQueue
            restaurant.orders.put(new Order(customer, this, food));
        } catch (InterruptedException e) {
            println(this + " placeOrder interrupted");
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                // 在菜品完成前阻塞
                Plate plate = filledOrders.take();
                println(this + "received " + plate + " delivering to "
                        + plate.getOrder().getCustomer());
                plate.getOrder().getCustomer().deliver(plate);
            }
        } catch (InterruptedException e) {
            println(this + "Interrupted");
        }
        println(this + "off duty");
    }

    @Override
    public String toString() {
        return "WaitPerson " + id + " ";
    }
}

// 厨师
class Chef implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final Restaurant restaurant;
    private static Random random = new Random(47);

    public Chef(Restaurant restaurant) {
        this.restaurant = restaurant;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                // 出现订单前阻塞
                Order order = restaurant.orders.take();
                Food requestItem = order.item();
                // 准备订单的时间
                TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
                Plate plate = new Plate(order, requestItem);
                order.getWaitPerson().filledOrders.put(plate);
            }
        } catch (InterruptedException e) {
            println(this + " interrupted");
        }
        println(this + " off duty");
    }

    @Override
    public String toString() {
        return "Chef " + id + " ";
    }
}


// 餐厅
class Restaurant implements Runnable {
    private List<WaitPerson> waitPersons = new ArrayList<>();
    private List<Chef> chefs = new ArrayList<>();

    private ExecutorService exec;
    private static Random random = new Random(47);
    BlockingQueue<Order> orders = new LinkedBlockingQueue();

    public Restaurant(ExecutorService e, int nWaitPersons, int nChefs) {
        exec = e;
        for (int i = 0; i < nWaitPersons; i++) {
            WaitPerson waitPerson = new WaitPerson(this);
            waitPersons.add(waitPerson);
            exec.execute(waitPerson);
        }
        for (int i = 0; i < nChefs; i++) {
            Chef chef = new Chef(this);
            chefs.add(chef);
            exec.execute(chef);
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                // 新客户到来并指定服务员
                WaitPerson wp = waitPersons.get(random.nextInt(waitPersons.size()));
                Customer customer = new Customer(wp);
                exec.execute(customer);
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch (InterruptedException e) {
            System.out.println("Restaurant interrupted");
        }
        System.out.println("Restaurant closing");
    }
}

public class RestaurantWithQueues {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        Restaurant restaurant = new Restaurant(exec, 5, 2);
        exec.execute(restaurant);

        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}
